﻿using System;
using System.Windows.Forms;
using Auxiliary.VecMath;
using Simple3DRedactor.Domain;
using Tao.OpenGl;

namespace Simple3DRedactor.OpenGLViewer
{
    class PerspectiveRenderer : Renderer
    {
        private static readonly Vector3D DirVec = new Vector3D(0.0f, 0.0f, -1.0f);
        private static readonly Vector3D UpVec = new Vector3D(0.0f, 1.0f, 0.0f);
        private static readonly Vector3D LeftVec = new Vector3D(-1.0f, 0.0f, 0.0f);

        private const float PosStep = 0.5f;

        private Vector3D _pos = new Vector3D(10.0f, 10.0f, 10.0f);

        private float _xRot = 45.0f;
        private float _yRot = -45.0f;
        
        private int _oldx;
        private int _oldy;
        private bool _moving;

        private bool _enableLighting = true;

        public PerspectiveRenderer(OpenGLViewer viewer) : base(viewer)
        {
        }

        public override void LoadProjectionMatrix(int width, int height)
        {
            Glu.gluPerspective(60.0f, (float)width / height, 0.1f, 100.0f);
        }

        private static Vector3D Rotate(Vector3D v, float xRot, float yRot)
        {
            var t1 = new Vector3D(
                    v.X,
                    (float)(v.Y * Math.Cos(-xRot / 180.0 * Math.PI) - v.Z * Math.Sin(-xRot / 180.0 * Math.PI)),
                    (float)(v.Y * Math.Sin(-xRot / 180.0 * Math.PI) + v.Z * Math.Cos(-xRot / 180.0 * Math.PI))
                );

            var t2 = new Vector3D(
                    (float)(t1.X * Math.Cos(yRot / 180.0 * Math.PI) - t1.Z * Math.Sin(yRot / 180.0 * Math.PI)),
                    t1.Y,
                    (float)(t1.X * Math.Sin(yRot / 180.0 * Math.PI) + t1.Z * Math.Cos(yRot / 180.0 * Math.PI))
                );

            return t2;
        }

        public override void LoadModelViewMatrix()
        {
            var dir = Rotate(DirVec, _xRot, _yRot);
            dir += _pos;

            Glu.gluLookAt(_pos.X, _pos.Y, _pos.Z, dir.X, dir.Y, dir.Z, UpVec.X, UpVec.Y, UpVec.Z);
        }

        public override void Show(SceneView<IOpenGLObjectView> scene)
        {
            Gl.glDisable(Gl.GL_DEPTH_TEST);
            Gl.glDisable(Gl.GL_LIGHTING);

            Gl.glBegin(Gl.GL_LINES);
                // X axis
                Gl.glColor3f(1.0f, 0.0f, 0.0f);
                Gl.glVertex3i(0, 0, 0);
                Gl.glVertex3i(1000, 0, 0);

                // Y axis
                Gl.glColor3f(0.0f, 1.0f, 0.0f);
                Gl.glVertex3i(0, 0, 0);
                Gl.glVertex3i(0, 1000, 0);

                // Z axis
                Gl.glColor3f(0.0f, 0.0f, 1.0f);
                Gl.glVertex3i(0, 0, 0);
                Gl.glVertex3i(0, 0, 1000);
            Gl.glEnd();

            Gl.glEnable(Gl.GL_DEPTH_TEST);
            if (_enableLighting)
            {
                Gl.glEnable(Gl.GL_LIGHTING);

                Gl.glDisable(Gl.GL_LIGHT0);
                Gl.glDisable(Gl.GL_LIGHT1);
                Gl.glDisable(Gl.GL_LIGHT2);
                Gl.glDisable(Gl.GL_LIGHT3);
                Gl.glDisable(Gl.GL_LIGHT4);
                Gl.glDisable(Gl.GL_LIGHT5);
                Gl.glDisable(Gl.GL_LIGHT6);
                Gl.glDisable(Gl.GL_LIGHT7);
            }

            Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_FILL);

            RenderScene(scene);
        }

        public override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                _oldx = e.X;
                _oldy = e.Y;

                ClearSelection();
                TrySelectObject(e.X, e.Y);

                _moving = true;
            }
        }

        private static int Clamp(int val, int minVal, int maxVal)
        {
            return Math.Max(Math.Min(val, maxVal), minVal);
        }

        public override void OnMouseMove(MouseEventArgs e)
        {
            int mouseDx;
            int mouseDy;

            if (_moving)
            {
                mouseDx = _oldx - e.X;
                mouseDy = _oldy - e.Y;
            }
            else
            {
                mouseDx = 0;
                mouseDy = 0;
            }

            const int mouseClamp = 500;
            mouseDx = Clamp(mouseDx, -mouseClamp, mouseClamp);
            mouseDy = Clamp(mouseDy, -mouseClamp, mouseClamp);

            const float mouseStep = 0.5f;
            _yRot -= mouseDx * mouseStep;
            _xRot -= mouseDy * mouseStep;

            _oldx = e.X;
            _oldy = e.Y;
        }

        public override void OnMouseUp(MouseEventArgs e)
        {
            _moving = false;
        }

        public override void OnMouseWheel(MouseEventArgs e)
        {
            var dir = Rotate(DirVec, _xRot, _yRot);

            if (e.Delta > 0)
                _pos += PosStep * dir;
            else
                _pos -= PosStep * dir;
        }

        public override void OnKeyDown(KeyEventArgs e)
        {
            var dir = Rotate(DirVec, _xRot, _yRot);
            var left = Rotate(LeftVec, _xRot, _yRot);

            switch (e.KeyCode)
            {
                case Keys.W:
                    _pos += PosStep * dir;
                    break;
                case Keys.S:
                    _pos -= PosStep * dir;
                    break;
                case Keys.A:
                    _pos += PosStep * left;
                    break;
                case Keys.D:
                    _pos -= PosStep * left;
                    break;
                case Keys.Q:
                    _pos += PosStep * UpVec;
                    break;
                case Keys.E:
                    _pos -= PosStep * UpVec;
                    break;
                case Keys.L:
                    _enableLighting = !_enableLighting;
                    break;
            }
        }
    }
}
